// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_LOCK_SPIN_H_
#define LIBV_LOCK_SPIN_H_

#include <libv/atomic.h>
#include <libv/lock.h>
#include <libv/thread_id.h>
#include <libv/stdint.h>


namespace libv
{

// Не предназначен для использования.
// Необходим для устранения дублирования.
class SpinImpl : Uncopyable
{
  public:
    SpinImpl() : var_(0) {}
    ~SpinImpl() {}

    bool TryLock()
    {
        // Уменьшаем количество бесполезных записей в память:
        // меньше блокировок шины + реже инвалидируется кэш.
        if ( this->var_ != 0 ) return false;

        // Этот вариант быстрее на 10-20%, чем Test-and-Swap.
        // Он проигрывает 10% только в случаях постоянных коллизий.
        if ( AtomicOp32::Increment(&this->var_) == 0 ) return true;

        AtomicOp32::Decrement(&this->var_);
        return false;
    }


    void Lock()
    {
        while ( TryLock() == false ) continue;
    }


    void Unlock()
    {
        AtomicOp32::Decrement(&this->var_);
    }


  private:
    AtomicType32 var_;
};


////////////////////////////////////////////////////////////////////////////////


// Занимает 4 байта + vptr == 8(12) байт
class Spin : public LockableResourceInterface
{
  public:
    /*virtual*/ bool TryLock()
    {
        return spin_impl_.TryLock();
    }


    /*virtual*/ void Lock()
    {
        spin_impl_.Lock();
    }


    /*virtual*/ void Unlock()
    {
        spin_impl_.Unlock();
    }


  private:
    SpinImpl spin_impl_;
};


////////////////////////////////////////////////////////////////////////////////


class RecursiveSpin : public RecursiveLockableResourceInterface
{
  public:
    RecursiveSpin()
        : rec_count_(0),
          cur_thread_id_(0) {}

    ~RecursiveSpin() {}


    /*virtual*/ void Lock()
    {
        const ThreadID cur_thread_id = thread_id();
        if ( cur_thread_id_ == cur_thread_id )
        {
            rec_count_++;
            return;
        }

        spin_impl_.Lock();
        cur_thread_id_ = cur_thread_id;
        rec_count_++;
    }


    /*virtual*/ bool TryLock()
    {
        const ThreadID cur_thread_id = thread_id();
        if ( cur_thread_id == cur_thread_id_ || spin_impl_.TryLock() )
        {
            cur_thread_id_ = cur_thread_id;
            rec_count_++;
            return true;
        }

        return false;
    }


    /*virtual*/ void Unlock()
    {
        if ( --rec_count_ == 0 )
        {
            spin_impl_.Unlock();
            cur_thread_id_ = 0;
        }
    }


  private:
    SpinImpl spin_impl_;
    uint32_t rec_count_;
    ThreadID cur_thread_id_;
};


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


/** Нерекурсивный спин чтения/записи. */
class ReadWriteSpin : public ReadWriteLockableResourceInterface
{
  public:
    ReadWriteSpin() : var_(0) {}


    /*virtual*/ bool TryReadLock()
    {
        // Уменьшаем количество бесполезных записей в память:
        // меньше блокировок шины + реже инвалидируется кэш.
        if ( this->var_ < 0 ) return false;

        if ( AtomicOp32::Increment(&this->var_) >= 0 ) return true;

        AtomicOp32::Decrement(&this->var_);
        return false;
    }


    /*virtual*/ void ReadLock()
    {
        while ( TryReadLock() == false ) continue;
    }


    /*virtual*/ bool TryWriteLock()
    {
        // Уменьшаем количество бесполезных записей в память:
        // меньше блокировок шины + реже инвалидируется кэш.
        if ( this->var_ < 0 ) return false;

        if ( AtomicOp32::Sub(&this->var_, (int)ALL_THREADS) >= 0 )
        {
            // Ждем, пока не уйдут читатели
            while ( this->var_ != -ALL_THREADS ) continue;
            return true;
        }
        else
        {
            AtomicOp32::Add(&this->var_, (int)ALL_THREADS);
            return false;
        }
    }


    /*virtual*/ void WriteLock()
    {
        while ( TryWriteLock() == false ) continue;
    }


    /*virtual*/ void ReadUnlock()
    {
        AtomicOp32::Decrement(&this->var_);
    }


    /*virtual*/ void WriteUnlock()
    {
        AtomicOp32::Add(&this->var_, (int)ALL_THREADS);
    }


  private:
    enum { ALL_THREADS = 1U << 20 };
    AtomicType32 var_;
};

}  // libv

#endif  // LIBV_LOCK_SPIN_H_
